/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2008 Apple Inc.  All Rights Reserved.
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 *
 */
 
/*
    File:       QTSSMP3StreamingModule.h

    Contains:   Handle ShoutCast/IceCast-style MP3 streaming.
    
    Written by: Steve Ussery    

*/

#ifndef __QTSSMP3STREAMINGMODULE_H__
#define __QTSSMP3STREAMINGMODULE_H__

#include "QTSS.h"
#include "OSQueue.h"
#include "OSMutex.h"
#include "OSHashTable.h"
#include "OSBufferPool.h"

extern "C"
{
    EXPORT QTSS_Error QTSSMP3StreamingModule_Main(void* inPrivateArgs);
}

#define kURLBufferSize 1024
#define kSongNameBufferSize 1024
#define kHeaderBufferSize 1024
#define kRequestBufferSize 512
#define kHostNameBufferSize 256
#define kUserAgentBufferSize 256
#define kClientMetaInt 24576

// The client poll interval in milliseconds
#define kClientPollInterval 253

// FORWARD CLASS DEFINES

class MP3ClientSessionRef;
class MP3ClientSession;
class MP3ClientQueue;
class MP3BroadcasterSession;
class MP3BroadcasterQueue;
class MP3SessionRef;
class MP3SessionRefKey;
class MP3SessionTable;

enum {
    kMP3UndefinedSessionType = 0,
    kMP3BroadcasterSessionType,
    kMP3ClientSessionType
};

//
// MP3Session -- This is a base class to hold all the MP3 Session state info.
// We will subclass it for MP3 Broadcaster sessions and MP3 Client sessions.
// (See the MP3BroadcasterSession & MP3ClientSession classes defined below.)
//
class MP3Session 
{
public:
                    MP3Session();

                    MP3Session(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream);
                    
    virtual         ~MP3Session();
    
    // The IsA() method is a mechanism for subclasses of this class to
    // identify their type. It is kMP3UndefinedSessionType for this
    // base class.
    
    virtual UInt8   IsA() const;
    
    // Session data field accessor methods
    
    QTSS_RTSPSessionObject  GetSession() { return fSession; }
    
    void            SetSessionID(UInt32 sessID) { fSessID = sessID; }
    
    UInt32          GetSessionID() { return fSessID; }
    
    QTSS_StreamRef  GetStreamRef() { return fStream; }
    
    UInt16          GetState() { return fState; }
    
    void            SetState(UInt16 state) { fState = state; }
    
    UInt16          GetResult() { return fResult; }
    
    void            SetResult(UInt16 result) { fResult = result; }
    
    static SInt32   GetTotalNumMP3Sessions() { return sTotalNumMP3Sessions; }

protected:
    
    void            SetSession(QTSS_RTSPSessionObject session) { fSession = session; }

    void            SetStreamRef(QTSS_StreamRef stream) { fStream = stream; }
    
private:
    
    static SInt32   sTotalNumMP3Sessions;

    UInt16          fState;
    UInt16          fResult;
    QTSS_StreamRef  fStream;
    QTSS_RTSPSessionObject  fSession;
    UInt32          fSessID;
};
    
//
// MP3BroadcasterSession -- This is a class to hold all the MP3 Broadcaster
// session-related state info. There is a global queue of these managed by the
// server. Each instance of this class must have a unique mountpoint name
// string which clients will use to identify the broadcast stream they are
// "tuning" into.
//
class MP3BroadcasterSession : public MP3Session
{
public:


    // MP3BroadcasterSession states

    enum {
        kBroadcasterInitState = 0,
        kBroadcasterValidatePasswordState,
        kBroadcasterGetHeaderState,
        kBroadcasterRecvDataState,
        kBroadcasterShutDownState
    };

                    MP3BroadcasterSession(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream);
                    
    virtual         ~MP3BroadcasterSession();
    
    // The IsA() method is a mechanism for subclasses of the MP3Session class
    // to identify their type. It is kMP3BroadcasterSessionType for this
    // subclass.
    
    virtual UInt8   IsA() const;
    
    // Session state control methods
    
    QTSS_Error      ExecuteState();
    
    void            InitBroadcastSessionState();
    
    void            AcceptBroadcastSessionState();
    
    void            AcceptPasswordState();
    
    void            AcceptDataStreamState();
    
    void            ShutDownState();
    
    // MP3 Client handling methods
    
    QTSS_Error      AddClient(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream);
    
    QTSS_Error      RemoveClient(QTSS_RTSPSessionObject sess);
    
    Bool16          IsMyClient(QTSS_RTSPSessionObject sess);

    // Session data field accessor methods

    void            SetMountpoint(char* mp);
    
    void            SetMountpoint(StrPtrLen& mp);
    
    Bool16          MountpointEqual(char* mp);
    
    Bool16          MountpointEqual(StrPtrLen& mp);
    
    char*           GetMountpoint() { return fMountpoint; }
    
    char*           GetHeader() { return fHeader; }
    
    void            SetSongName(char* sn);
    
    char*           GetSongName() { return fSongName; }
    
    // Session data handlers
    
    QTSS_Error      SendOKResponse();

    QTSS_Error      GetBroadcastHeaders();

    QTSS_Error      GetBroadcastData();

    QTSS_Error      SendClientsData();

    void            PreflightClients();

protected:
    
    void            TerminateHeaders();

private:
    
                    MP3BroadcasterSession();
                    
    MP3ClientQueue* fMP3ClientQueue;
    UInt32          fDataBufferLen;
    UInt32          fDataBufferSize;
    char            fMountpoint[kURLBufferSize];
    char            fHeader[kHeaderBufferSize];
    char            fSongName[kSongNameBufferSize];
    char*           fBuffer;
    OSMutex         fSongNameMutex;
    Bool16          fNewSongName;
};

//
// MP3ClientSession -- This is a class to hold all the MP3 client
// session-related state info. These class instances are always owned by a
// instance of the MP3BroadcasterSession class which has a queue of these.
//
class MP3ClientSession : public MP3Session
{
public:


    // MP3ClientSession states

    enum {
        kClientInitState = 0,
        kClientSendResponse,
        kClientSendDataState,
        kClientShutDownState
    };

                    MP3ClientSession(QTSS_RTSPSessionObject sess, 
                                                 QTSS_StreamRef stream,
                                                 MP3BroadcasterSession* owner);
                    
    virtual         ~MP3ClientSession();
    
    // The IsA() method is a mechanism for subclasses of the MP3Session class
    // to identify their type. It is kMP3ClientSessionType for this subclass.
    
    virtual UInt8   IsA() const;
    
    // Session stream handlers
    
    QTSS_Error      SendResponse();

    QTSS_Error      SendMP3Data(char* buffer, UInt32 bufferlen);

    QTSS_Error      RetrySendData();

    QTSS_Error      SendMetaData();
    
    // Session data field accessor methods
    
    void            SetHeader(char* header);
    
    char*           GetHeader() { return fHeader; }
    
    char*           GetHostName() { return fHostName; }
    
    char*           GetUserAgent() { return fUserAgent; }
    
    void            SetSongName(char* sn);
    
    char*           GetSongName() { return fSongName; }
    
    MP3BroadcasterSession*  GetOwner() { return fOwner; }
    
    void            SetOwner(MP3BroadcasterSession* owner) { fOwner = owner; }
    
    Bool16          WasBlocked() { return fWasBlocked; }

    Bool16          WantsContentLength() { return fNeedsContentLength; }

    void            SetRequest(char* req) { ::strcpy(fRequestBuffer, req); }

    char*           GetRequest() { return fRequestBuffer; }

    UInt32          GetTotalCount() { return fBytesSent; }

    SInt64          GetConnectTime() { return fConnectTime; }
    
private:
    
                    MP3ClientSession();
                    
    void            UpdateBitRateInternal(const SInt64& curTime);
    
    void            ParseRequestParams(QTSS_StreamRef stream);

    UInt32          fBytesSent;
    UInt32          fCurrentBitRate;
    UInt32          fLastBitRateBytes;
    SInt64          fLastBitRateUpdateTime;
    SInt64          fConnectTime;
    UInt32          fSendCount;
    UInt32          fRetryCount;
    MP3BroadcasterSession*  fOwner;
    char            fHeader[kHeaderBufferSize];
    char            fHostName[kHostNameBufferSize];
    char            fUserAgent[kUserAgentBufferSize];
    char            fSongName[kSongNameBufferSize];
    OSMutex         fSongNameMutex;
    Bool16          fNewSongName;
    Bool16          fWasBlocked;
    SInt64          fBlockTime;
    Bool16          fNeedsContentLength;
    Bool16          fWantsMetaData;
    char            fRequestBuffer[kRequestBufferSize];
    QTSS_Object     fQTSSObject;
};

//
// MP3SessionRef -- This class is just a wrapper class for handling the
// mapping of RTSP Session refs to the corresponding MP3 Session class refs.
// It will be the an element of our hash table in the MP3SessionTable class.
//
class MP3SessionRef 
{
public:
                    MP3SessionRef(MP3Session* mp3Session);
                    
                    ~MP3SessionRef();
    
private:
    
    MP3Session*     GetMP3Session() { return fMP3Session; }

    QTSS_RTSPSessionObject  GetSession() { return fMP3Session->GetSession(); }

    MP3SessionRef*  fNextHashEntry;
    PointerSizedInt fHashValue;
    MP3Session*     fMP3Session;
    
    friend class MP3SessionRefKey;
    friend class OSHashTable<MP3SessionRef, MP3SessionRefKey>;
    friend class OSHashTableIter<MP3SessionRef, MP3SessionRefKey>;
    friend class MP3SessionTable;
};

//
// MP3SessionRefKey -- This class is used to generate hash keys for looking
// up values in our MP3SessionTable.
//
class MP3SessionRefKey 
{
public:
                    MP3SessionRefKey(MP3SessionRef* mp3SessRef);

                    MP3SessionRefKey(QTSS_RTSPSessionObject rtspSessRef);
                    
                    ~MP3SessionRefKey();
    
private:
    
    PointerSizedInt	GetHashKey() { return fHashValue; }
    
    friend int operator ==(const MP3SessionRefKey &key1, const MP3SessionRefKey &key2)
    {
        return (key1.fHashValue == key2.fHashValue);
    }

    MP3SessionRef*  fKeyValue;
    PointerSizedInt fHashValue;
    MP3Session*     fMP3Session;
    
    friend class OSHashTable<MP3SessionRef, MP3SessionRefKey>;
};

typedef OSHashTable<MP3SessionRef, MP3SessionRefKey> MP3SessRefHashTable;
typedef OSHashTableIter<MP3SessionRef, MP3SessionRefKey> MP3SessRefHashTableIter;

//
// MP3SessionTable -- This class provides a way to map RTSP Session references
// into the corresponding MP3Session class instances if any.
//
class MP3SessionTable 
{
public:

    enum
    {
        kDefaultTableSize = 19
    };
    
    // tableSize is the number of hash buckets not the number of entries.

                    MP3SessionTable(UInt32 tableSize = kDefaultTableSize);
                    
                    ~MP3SessionTable();
    
    UInt32          GetNumSessionsInTable() { return (UInt32) fTable.GetNumEntries(); }
    
    // Attempt to add a new MP3Session ref to the table's map.
    // returns true on success or false if it fails.
    Bool16          RegisterSession(MP3Session* session);
    
    // Given an QTSS_RTSPSessionObject resolve it into a MP3Session class
    // reference. Returns NULL if there's none in out map.
    MP3Session*     Resolve(QTSS_RTSPSessionObject rtspSession);

    // Attempt to remove a  MP3Session ref from the table's map.
    // returns true on success or false if it fails.
    Bool16          UnRegisterSession(MP3Session* session);

private:
    
    MP3SessRefHashTable fTable;
    OSMutex             fMutex;
};

//
// MP3ClientQueue -- This is a class manages a queue of MP3Client objects.
// Every MP3BroadcasterSession class instance contains one of these objects.
//
class MP3ClientQueue 
{
public:

                    MP3ClientQueue();
                    
                    ~MP3ClientQueue();
    
    UInt32          GetNumClients() { return fQueue.GetLength(); }

    QTSS_Error      AddClient(QTSS_RTSPSessionObject clientsess, QTSS_StreamRef clientstream, 
                                            MP3BroadcasterSession* owner);

    QTSS_Error      RemoveClient(QTSS_RTSPSessionObject clientsess);

    Bool16          InQueue(QTSS_RTSPSessionObject clientsess);

    QTSS_Error      SendToAllClients(char* buffer, UInt32 bufferlen);

    void            PreflightClients(char* songname);

private:
    
    void            TerminateClients();

    OSMutex         fMutex;
    OSQueue         fQueue;
};

//
// MP3BroadcasterQueue -- This is a class manages a queue of MP3Broadcaster objects.
// There is only one global instance of this class owned by the server.
//
class MP3BroadcasterQueue 
{
public:

                    MP3BroadcasterQueue();
                    
                    ~MP3BroadcasterQueue();
    
    UInt32          GetNumBroadcasters() { return fQueue.GetLength(); }

    QTSS_Error      CreateBroadcaster(QTSS_RTSPSessionObject bcastsess, QTSS_StreamRef bcaststream, StrPtrLen& mountpt);

    QTSS_Error      RemoveBroadcaster(QTSS_RTSPSessionObject bcastsess);

    QTSS_Error      RemoveClient(QTSS_RTSPSessionObject clientsess);

    Bool16          InQueue(QTSS_RTSPSessionObject bcastsess);

    Bool16          IsActiveClient(QTSS_RTSPSessionObject clientsess);

    MP3BroadcasterSession*  FindByMountPoint(char* mountpoint);

    MP3BroadcasterSession*  FindByMountPoint(StrPtrLen& mountpoint);

    MP3BroadcasterSession*  FindBySession(QTSS_RTSPSessionObject bcastsess);

    void            TerminateAllBroadcastSessions();

private:
    
    OSMutex         fMutex;
    OSQueue         fQueue;
};

#endif // __QTSSMP3STREAMINGMODULE_H__

